/**
 * 把一个对象转换成响应式对象
 * 
 * 1. 接收一个对象作为参数
 * 2. 创建拦截器handler，设置get/set/deletProperty
 * 3. 返回proxy对象
*/
const isObject = val => val !== null && typeof val === 'object'
const convert = target => isObject(target) ? reactive(target) : target

function reactive(target){
  if(!isObject(target)) return target

  const handler = {
    get(target, key, receiver) {
      // 收集依赖
      track(target, key)

      const result = Reflect.get(target, key, receiver)
      // 如果获取的属性值是一个对象，则要把这个对象也转换为响应式对象
      return convert(result)
    },

    set(target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver)
      let result = true

      if(oldValue !== value) {
        result = Reflect.set(target, key, value, receiver)

        // 触发更新
        trigger(target, key)
      }

      return result
    },

    deleteProperty(target, key) {
      const result = Reflect.defineProperty(target, key)
      if(target.hasOwnProperty(key) && result) {
        // 触发更新
        trigger(target, key)
      }

      return result
    }
  }

  return new Proxy(target, handler)
}

let activeEffct = null
export function effect(callback){
  activeEffct = callback

  callback() // callback中可以访问响应式对象属性，所以要收集依赖，就是把响应式对象的属性和这个callback关联起来。属性改变时，调用与之关联的callback

  // 依赖收集完毕后，清空activeEffect
  activeEffct = null
}

let targetMap = new WeakMap() // targetMap中存放的是每个响应是对象，和其depsMap的映射关系
export function track(target, key) {  // 收集依赖，粒度是对象中的每个属性
  if(!activeEffct) return

  let depsMap = targetMap.get(target)

  if(!depsMap) {
    depsMap = new Map() // depsMap中存放的是当前响应式对象target的每个属性和属性对应的callback集合的映射关系
    targetMap.set(target, depsMap)
  }

  let dep = depsMap.get(key)

  if(!dep) {
    dep = new Set() // 依赖存放的地方
    depsMap.set(key, dep)
  }

  dep.add(activeEffct)
}

// 触发更新，找到响应式对象target对应的depsMap，在depsMap中找到指定key对应的deps集合，遍历调用该集合中的effect
export function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if(!depsMap) return

  const deps = depsMap.get(key)
  if(!deps) return

  deps.forEach(effect => effect())
}

export function ref(raw) {
  if(isObject(raw) && raw.__v_isRef) {
    return
  }
  let value = convert(raw)

  const r = {
    __v_isRef: true,
    get value (){
      track(r, 'value')
      return value
    },
    set value (newValue) {
      if(newValue !== value) {
        raw = newValue
        value = convert(raw)
        trigger(r, 'value')
      }
    }
  }

  return r
}


// computed就是对effect函数的进一步封装，返回一个响应式的对象，通过value去访问